package particle_simulator_chem; 
 
import java.util.ArrayList; 
 
import org.eclipse.swt.graphics.Color; 
 
import chemistry.Element; 
import chemistry.ElementList; 
import chemistry.FormulaParser; 
 
public class Boid { 
    private Point3D location; 
    private double xVel; 
    private double yVel; 
    private double zVel; 
    private Color color; 
    private String text; 
    private String raw_formula; 
    private ArrayList<Element> formula; 
    private boolean compound; 
 
//////////////////////////////////Initialization//////////////////////////////////// 
     
    //2D constructor 
    public Boid(double x, double y, double xVel, double yVel, Color color, String text, String formula, ElementList elements) { 
        this.location = new Point3D(x, y); 
        this.xVel = xVel; 
        this.yVel = yVel; 
        this.color = color; 
        this.setText(text);  
        this.setRawFormula(formula); 
        this.formula = FormulaParser.getElements(formula, elements); 
    } 
     
    //3D constructor 
    public Boid(double x, double y, double z, double xVel, double yVel, double zVel, Color color, String text, String formula, ElementList elements) { 
        this.location = new Point3D(x, y, z); 
        this.xVel = xVel; 
        this.yVel = yVel; 
        this.zVel = zVel; 
        this.color = color; 
        this.setText(text);  
        this.setRawFormula(formula); 
        this.formula = FormulaParser.getElements(formula, elements); 
    } 
 
//////////////////////////////////Library Functions///////////////////////////////// 
     
    /** 
     * Calculates the distance between two points in 3-space using the pythagorean theorem. 
     *  
     * Formula used for calculation: 
     *         d = sqrt(((x1 - x2) ^ 2) + ((y1 - y2) ^ 2) + ((z1 - z2) ^ 2)) 
     *  
     * @param x1 
     * @param y1 
     * @param z1 
     * @param x2 
     * @param y2 
     * @param z2 
     * @return distance 
     */ 
    public static double pTheorem3D(double x1, double y1, double z1, double x2, double y2, double z2) { 
        return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(z1 - z2, 2)); 
    } 
 
    /** 
     * Calculates the horizontal angle between two points in 3-space. 
     *  
     * Formula used for calculation: 
     *         hAngle = atan2(y2 - y1, x2 - x1)   
     *  
     * @param x1 
     * @param y1 
     * @param x2 
     * @param y2 
     * @return hAngle 
     */ 
    public static double hAngle(double x1, double y1, double x2, double y2) { 
        return Math.atan2(y2 - y1, x2 - x1); 
    } 
     
    /** 
     * Calculates the vertical angle between two points in 3-space. 
     *  
     * Formula used for calculation: 
     *         vAngle = atan2(y2 - y1, sqrt((x1 - x2) ^ 2 + (z1 - z2) ^ 2))   
     *  
     * @param x1 
     * @param y1 
     * @param z1 
     * @param x2 
     * @param y2 
     * @param z2 
     * @return vAngle 
     */ 
    public static double vAngle(double x1, double y1, double z1, double x2, double y2, double z2) { 
        return Math.atan2(y2 - y1, Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(z1 - z2, 2))); 
    } 
     
    /** 
     * Calculates the point on the given 2-sphere with a horizontal and a vertical angle. 
     *  
     * Formulae used for calculation: 
     *         x = sphereX + sin(hAngle) * sin(vAngle) * sphereRadius 
     *         y = sphereY + sin(vAngle) * sphereRadius; 
     *         z = sphereZ + cos(hAngle) * sin(vAngle) * sphereRadius 
     * 
     * Sphere x, y, and z correspond to the center of the provided 2-sphere. 
     *  
     * @param hAngle 
     * @param vAngle 
     * @param sphereX 
     * @param sphereY 
     * @param sphereZ 
     * @param sphereRadius 
     * @return point 
     */ 
    public static Point3D pointOnSphere(double hAngle, double vAngle, double sphereX, double sphereY, double sphereZ, double sphereRadius) { 
        return new Point3D(sphereX + Math.sin(hAngle) * Math.sin(vAngle) * sphereRadius,  
                sphereY + Math.sin(vAngle) * sphereRadius, 
                sphereZ + Math.cos(hAngle) * Math.sin(vAngle) * sphereRadius); 
    } 
     
    /** 
     * Finds the horizontal and vertical angles between this boid and the provided point in 3-space using hAngle(...) and vAngle(...) 
     *  
     * @param otherPoint 
     * @return angles 
     */ 
    public double[] angleTo(Point3D otherPoint) { 
        double[] angles = new double[2]; //Dimension - 1 angles are needed 
        angles[0] = hAngle(this.getX(), this.getY(), otherPoint.getX(), otherPoint.getY()); 
        angles[1] = vAngle(this.getX(), this.getY(), this.getZ(), otherPoint.getX(), otherPoint.getY(), otherPoint.getZ()); 
        return angles; 
    } 
     
    /** 
     * Finds the distance between two points in 3-space using pTheorem3D(...) 
     *  
     * @param otherPoint 
     * @return distance 
     */ 
    public double distanceTo(Point3D otherPoint) { 
        return pTheorem3D(this.getX(), this.getY(), this.getZ(), otherPoint.getX(), otherPoint.getY(), otherPoint.getZ()); 
    } 
 
//////////////////////////////////Setters and Getters/////////////////////////////// 
     
    public double getX() { 
        return this.location.getX(); 
    } 
     
    public double getY() { 
        return this.location.getY(); 
    } 
     
    public double getZ() { 
        return this.location.getZ(); 
    } 
     
    public Point3D getLocation() { 
        return this.location; 
    } 
     
    public Color getColor() { 
        return this.color; 
    } 
     
    public double getXVel() { 
        return this.xVel; 
    } 
     
    public double getYVel() { 
        return this.yVel; 
    } 
     
    public void setXVel(double xVel) { 
        this.xVel = xVel; 
    } 
     
    public void setYVel(double yVel) { 
        this.yVel = yVel; 
    } 
     
    public double getUpdateRadius() { 
        return (double) this.getAttrRadius(); 
    } 
     
    public void setText(String text) { 
        this.text = text; 
    } 
     
    public String getText() { 
        return text; 
    } 
     
    public ArrayList<Element> getFormula() { 
        return this.formula; 
    } 
     
    public int getAttrRadius() { 
        int radSum = 0; 
        for (Element e: this.formula) { 
            radSum += e.getAtomicRadius(); 
        } 
        return radSum / 25 + 8; 
    } 
     
    public double getAttrStrength() { 
        int massSum = 0; 
        for (Element e: this.formula) { 
            massSum += e.getMass(); 
        } 
        return massSum / 3300.0; 
    } 
     
    public double getRepulsionStrength() { 
        int massSum = 0; 
        for (Element e: this.formula) { 
            massSum += e.getMass(); 
        } 
        return massSum / 920.0; 
    } 
     
    public int getRepulsionRadius() { 
        int attr = this.getAttrRadius(); 
        return (int) (attr - .35 * attr); 
    } 
     
    public double getReactionRadius() { 
        int chargeSum = 0; 
        for (Element e: this.formula) { 
            chargeSum += e.getCharge(); 
        } 
        if (chargeSum >= 0 && this.formula.get(0).getElectronegativity() != 0) { 
            double eSum = 0; 
            for (Element e: this.formula) { 
                eSum += e.getIonizationEnergy(); 
            } 
            eSum /= this.formula.size(); 
            return eSum + this.getRepulsionRadius(); 
        } else { 
            double eSum = 0; 
            for (Element e: this.formula) { 
                eSum += e.getElectronegativity(); 
            } 
            eSum /= this.formula.size(); 
            return eSum; 
        } 
    } 
     
//////////////////////////////////Updaters////////////////////////////////////////// 
     
    public void updatePosition() { 
        this.location.setX(this.getX() + this.xVel); //identical to +=, modified for private fields 
        this.location.setY(this.getY() + this.yVel); 
        if ((Double) this.zVel != null) { 
            this.location.setZ(this.getZ() + this.zVel); 
        }     
    } 
     
    public Boid updateNeighborVelocities(ArrayList<Boid> neighbors) { 
        Boid react = this; 
        for(Boid boid : neighbors) { //for each boid in neighbors 
            double distance = this.distanceTo(boid.getLocation()); 
            double[] angle = this.angleTo(boid.getLocation()); 
            if (distance <= this.getReactionRadius()) { 
                react = boid; 
            } 
            if(distance <= this.getAttrRadius()) { 
                double strength = (this.getAttrRadius() / (distance + 0.01)) * this.getAttrStrength(); //Adding a small number to distance prevents a divide by zero error (which would occur if two particles occupied the same position) 
                boid.xVel -= Math.cos(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula)); 
                boid.yVel -= Math.sin(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula)); 
                if ((Double) this.zVel != null) { 
                    boid.zVel -= Math.tan(angle[1]) * strength * (75.0 / FormulaParser.getMass(boid.formula)); 
                }     
            } 
            if(distance <= this.getRepulsionRadius()) { 
                double strength = (this.getRepulsionRadius() / (distance + 0.01)) * this.getRepulsionStrength(); 
                boid.xVel += Math.cos(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula)); 
                boid.yVel += Math.sin(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula)); 
                if ((Double) this.zVel != null) { 
                    boid.zVel += Math.tan(angle[1]) * strength * (75.0 / FormulaParser.getMass(boid.formula)); 
                }     
            } 
        } 
        return react; 
    } 
 
    public void setRawFormula(String raw_formula) { 
        this.raw_formula = raw_formula; 
    } 
     
    public String getRawFormula() { 
        return raw_formula; 
    } 
 
    public void setCompound(boolean compound) { 
        this.compound = compound; 
    } 
 
    public boolean isCompound() { 
        return compound; 
    } 
 
    public void setZVel(double zVel) { 
        this.zVel = zVel; 
    } 
 
    public double getZVel() { 
        return zVel; 
    } 
} 

